package se.sll.service.catalogue.export.capacity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.Schedule;
import javax.ejb.ScheduleExpression;
import javax.ejb.SessionContext;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
import javax.jms.*;
import lombok.extern.slf4j.Slf4j;
import se.sll.service.catalogue.common.data.ListOfStrings;
import se.sll.service.catalogue.common.exception.ServiceErrorCode;
import se.sll.service.catalogue.common.exception.ValidationException;
import se.sll.service.catalogue.common.message.ExpiringCapacitiesNotification;
import se.sll.service.catalogue.common.property.CatalogProperties;
import se.sll.service.catalogue.export.restclient.RestClientHelper;

/**
 *
 * @author Jiri Uosukainen
 */
@Slf4j
@Singleton
@Startup
public class ExpiringCapacityTimer {
    

    private static final String CLASS_NAME = ExpiringCapacityTimer.class.getName();
    
    private static final String DEFAULT_EXPIRING_CAPACITY_SCHEDULE = "DEFAULT_EXPIRING_CAPACITY_SCHEDULE";
    private static final String MANUAL_EXPIRING_CAPACITY_TIMER = "MANUAL_EXPIRING_CAPACITY_TIMER";
    
    @Resource
    SessionContext sessionContext;
    
    @Resource(mappedName = CatalogProperties.JMS_POOLED_CONNECTION_FACTORY)
    private ConnectionFactory connectionFactory;

//    @Resource(mappedName = CatalogProperties.JMS_SMTP_QUEUE_JNDI)
    @Resource(mappedName = "java:/"+CatalogProperties.JMS_SMTP_QUEUE_JNDI)
    private Queue capacityQueue;
    

    private static final String SCHEDULE_RUN_TIME_HOURS_DEFAULT = "2";
    private static final boolean USE_PERSISTENT_TIMERS = false;
    

//    @Schedule(minute = "*/1", hour = "*", persistent = USE_PERSISTENT_TIMERS, info = DEFAULT_EXPIRING_CAPACITY_SCHEDULE )
//    @Schedule(hour = SCHEDULE_RUN_TIME_HOURS_DEFAULT, persistent = USE_PERSISTENT_TIMERS, info = DEFAULT_EXPIRING_CAPACITY_SCHEDULE )
    @Schedule(dayOfWeek = "Sun, Wed",hour = SCHEDULE_RUN_TIME_HOURS_DEFAULT, persistent = USE_PERSISTENT_TIMERS, info = DEFAULT_EXPIRING_CAPACITY_SCHEDULE )
    private void scheduleRunning(){
        log.info("{}.scheduleRunning(): ENTER", CLASS_NAME);
        Date start = new Date();
        log.info("Schedule started : {} ", start);
        
        doWork(start);
                
        log.info("{}.scheduleRunning(): Elapsed time = {} ms", new Object[]{CLASS_NAME, (System.currentTimeMillis()-start.getTime())});
        log.info("{}.scheduleRunning(): EXIT", new Object[]{CLASS_NAME});
        
    }

    
    @Timeout
    private void timerRunning(Timer timer){
        log.info("{}.timerRunning(): ENTER", CLASS_NAME);
        Date start = new Date();
        log.info("Timer started : {} ", start);
        
        doWork(start);
        
        log.info("{}.timerRunning(): Elapsed time = {} ms", new Object[]{CLASS_NAME, (System.currentTimeMillis()-start.getTime())});
        log.info("{}.timerRunning(): EXIT", CLASS_NAME);
    }
    
    
    /**
     * Method for modifying the timer schedule
     * @param hours The new timer schedule value for hours, valid values are [0..23].
     * @throws se.sll.service.catalogue.common.exception.ValidationException
     * @see ScheduleExpression
     */
    public void setTimerSchedule(String hours) throws ValidationException{
        log.info("{}.setTimerSchedule(): ENTER", CLASS_NAME);
        log.info("Indata : hours = {}", hours);
        
        boolean validInput = true;
        if(!validInput){
            log.warn("Input was not valid, no action will be taken");
            throw new ValidationException(ServiceErrorCode.EXPORT_INVALID_HOURS, "Invalid input, hours = " + hours);
        }
        
        try {
            // Clean up the current timer(s)
            Collection<Timer> timers = sessionContext.getTimerService().getTimers();
            for (Timer timer : timers) {
                log.info("Cancelling [Timer : {}, was set to expire {}]", new Object[]{timer.getInfo(), timer.getNextTimeout()});
                timer.cancel();
            }
            
            // Create new Schedule
            TimerConfig timerConfig = new TimerConfig(MANUAL_EXPIRING_CAPACITY_TIMER, USE_PERSISTENT_TIMERS);
            ScheduleExpression scheduleExpression = new ScheduleExpression();
            scheduleExpression.hour(hours);
            Timer newTimer = sessionContext.getTimerService().createCalendarTimer(scheduleExpression, timerConfig);
            log.info("Created [Timer : {}, set to expire {}]", new Object[]{newTimer.getInfo(), newTimer.getNextTimeout()});
            
            printCurrentTimerInfo(sessionContext.getTimerService()); // Logging
        } 
        catch (RuntimeException e) {
            log.error("Caught Exception when creating Timer schedule: {}, Message: {} ", new Object[]{e.getClass().getName(), e.getMessage()});
            log.info("ROLLBACK WILL BE SET");
            sessionContext.setRollbackOnly();
            throw e;
        }
        log.info("{}.setTimerSchedule(): EXIT", CLASS_NAME);
    }
    
    private void doWork(Date currentTime){
        log.debug("{}.doWork(): ENTER", CLASS_NAME);

        boolean activateNotification = isNotificationActive();
        log.info("{}.doWork(): Notification is {}", new Object[]{CLASS_NAME, (activateNotification?"ACTIVE":"INACTIVE")});
        
        if (activateNotification) {
            try {
                ListOfStrings units = findUnits(currentTime);
                if (units != null && !units.isEmpty()) {
                    Set<String> emails = findAdministratorEmails(units);
                    if (emails != null && !emails.isEmpty()) {
                        addToNotificationToQueue(currentTime, emails);
                    }
                }
            } catch (Exception e) {
                log.error("Caught Exception: {}, Message: {} ", new Object[]{e.getClass().getName(), e.getMessage()});
            }
        }
        printCurrentTimerInfo(sessionContext.getTimerService()); // Logging
        log.debug("{}.doWork(): EXIT", CLASS_NAME);
    }
    
    private ListOfStrings findUnits(Date currentTime) throws Exception{

        Integer nofDays = Integer.parseInt(CatalogProperties.getProperty(CatalogProperties.NAME.NOTIFICATION_CAPACITY_EXPIRATION_PERIOD_DAYS));

        log.info("Find units with times expiring within {} days from now ({})", new Object[]{nofDays, currentTime});
        
        StringBuilder url = new StringBuilder("");
        url.append(CatalogProperties.getProperty(CatalogProperties.NAME.CAPACITY_BASE_URL))
                .append("/capacity/findexpiring/")
                .append(nofDays);
        
        ListOfStrings result = RestClientHelper.sendJSONGetBA(url.toString(), ListOfStrings.class);       

        if(log.isDebugEnabled()){
            log.debug("Found units with expiring times : {}", result);
        }
        
        return result;
    }
    
    private Set<String> findAdministratorEmails(ListOfStrings unitIds) throws Exception{
        
        Set<String> result = new HashSet<>();
        
        if (unitIds != null && !unitIds.isEmpty()) {
            
            StringBuilder url = new StringBuilder("");
            url.append(CatalogProperties.getProperty(CatalogProperties.NAME.USER_BASE_URL)).append("/rest/users/emails");
            
            List<String> emails = RestClientHelper.sendJSONPostBA(url.toString(), unitIds.getValues(), ArrayList.class);
            
            if(log.isDebugEnabled()){
                log.debug("Found administrator emails : {}", emails);
            }
            else{
                log.info("Found {} administrator email addresses", (emails != null) ? emails.size(): "NONE");
                //log.info("Found {} administrator email addresses", emails);
            }
            
            if(emails != null && !emails.isEmpty()){
                for (String address : emails) {
                    if(address != null && !"".equals(address))
                    result.add(address);
                }
            }
        }
        else {
            log.info("No units to find administrators for");
        }
        return result;
    }
    
    private boolean isNotificationActive(){
        boolean activateNotification = false;
        try {
            activateNotification = Boolean.parseBoolean(CatalogProperties.getProperty(CatalogProperties.NAME.NOTIFICATION_CAPACITY_ACTIVE));
        }
        catch (Exception e) {
            log.error("Caught Exception parsing Notification configuration : {}, Message: {} ", new Object[]{e.getClass().getName(), e.getMessage()});
            activateNotification = false;
        }
        return activateNotification;
    }

    private void printCurrentTimerInfo(TimerService timerService){
    
        if (log.isInfoEnabled()) { // Print some timerinfo
            
            try {
                Collection<Timer> timers = timerService.getTimers();
                log.info("Existing timer INFORMATION :");
                for (Timer timer : timers) {
                    if (timer.isCalendarTimer()) {
                        ScheduleExpression sExp = timer.getSchedule();
                        log.info("Timer : {}, next run: {}, has schedule: {}", new Object[]{timer.getInfo(), timer.getNextTimeout(),sExp.toString()});
                    }
                    else{
                        log.info("Timer : {}, next run: {}", new Object[]{timer.getInfo(), timer.getNextTimeout()});
                    }
                }
            }
            catch (Exception e) { // IllegalStateException, EJBException
                log.warn("Caught Exception while printing timer INFO: Exception will be ignored: [Type: {}, Message: {}]"
                        , new Object[]{e.getClass().getName(), e.getMessage()});
            }
        }
    }
    
    @PostConstruct
    private void init (){
       printCurrentTimerInfo(sessionContext.getTimerService());
    }

    private void addToNotificationToQueue(Date currentTime, Set<String> emails) throws Exception{
        log.info("addToNotificationToQueue(): ENTER");

        Connection jmsConn = null;
        Session session = null;
        MessageProducer producer = null;
        try
        {
            jmsConn = connectionFactory.createConnection();
            session = jmsConn.createSession(true, Session.AUTO_ACKNOWLEDGE);
            producer = session.createProducer(capacityQueue);
            producer.setDeliveryMode(DeliveryMode.PERSISTENT); // Default

            ArrayList<String> recipients = new ArrayList<>(emails);

            ExpiringCapacitiesNotification msgObject = new ExpiringCapacitiesNotification(currentTime, recipients);

            ObjectMessage jmsMessage = session.createObjectMessage(msgObject);
            jmsMessage.setStringProperty("CATEGORY", "CAPACITY");
            producer.send(jmsMessage);
       }
       catch(JMSException e){
           log.warn("Caught JMSException", e);
           throw e;
       }
       catch(Exception e){
           log.warn("Caught Exception", e);
           throw e;
       }
       finally
       {
           try {producer.close();} catch (JMSException e) {log.warn("Failed to close MessageProducer: ", e);}
           try {session.close();} catch (JMSException e) {log.warn("Failed to close Session: ", e);}
           try {jmsConn.close();} catch (JMSException e) {log.warn("Failed to close Connection: ", e);}
       }
        
       log.info("addToNotificationToQueue(): EXIT");
    }
        
}
